#include "TcpConnection.h"
#include <Codec.h>
#include <fstream>
#include <RsaCrypto.h>
#include <sstream>
#include "Log.h"
#include "Room.h"

// 生成[fd对应的TcpConnection对象和channel对象], 将[fd的TcpConnection]注册到[fd的channel], 并将[fd的channel]添加到反应堆模型
TcpConnection::TcpConnection(int fd, EventLoop* evLoop)
	:_eventLoop(evLoop), _readBuf(new Buffer(10240)), _writeBuf(new Buffer(10240))
{
	_reply = new Communication;
	/*	通过bind和function可让1个类不通过[其他类的对象]来调用[其他类的非静态成员函数]
	 *		Communication可在不借助TcpConnection对象的情况下, 调用TcpConnection的非静态成员
	 *		函数addDeleteTask()、addWriteTask()
	 */
	auto delFunc = bind(&TcpConnection::addDeleteTask, this);
	auto writeFunc = bind(&TcpConnection::addWriteTask, this, placeholders::_1);
	_reply->setCallback(writeFunc, delFunc);

	_name = "Connection-" + to_string(fd);

	prepareSecretKey(); // 读取公钥, 将[公钥+公钥签名]写入[写缓冲区]

	// 将fd的读写回调设置为processRead、processWrite, 并且fd刚开始监视写事件(发送RSA密钥对的公钥给客户端)
	_channel = new Channel(fd, FdEvent::_WriteEvent, processRead, processWrite, destroy, this);

	// 将fd添加到反应堆，并监视fd的读事件
	_eventLoop->addTask(_channel, ElemType::ADD);
}

// 析构TcpConnection
TcpConnection::~TcpConnection() {
	if (_readBuf && _readBuf->readableSize() == 0
		&& _writeBuf && _writeBuf->readableSize() == 0)
	{
		_eventLoop->destroyChannel(_channel);
		delete _readBuf;
		delete _writeBuf;
	}
}

// 读取公钥, 将[公钥+公钥签名]写入[写缓冲区]
void TcpConnection::prepareSecretKey() {
	// 读公钥
	Room redis;
	redis.initEnvironment();
	string pubkey = redis.RsaSecKey("PublicKey");

	/* 准备[给客户端发送的Message对象]: _data1存储公钥, _data2存储公钥签名
	 *   [1] 公钥是没有经过加密的
	 *   [2] 公钥 --[哈希计算]--[私钥加密]--> 公钥签名
	*/
	Message msg;
	msg._resCode = RespondCode::RsaFenFa;
	msg._data1 = pubkey; // _data1 = 公钥
	RsaCrypto rsa;
	rsa.parseStringToKey(redis.RsaSecKey("PrivateKey"), RsaCrypto::PrivateKey);
	string data = rsa.sign(pubkey);
	msg._data2 = data; // _data2 = 公钥签名

	// 序列化
	Codec codec(&msg);
	data = codec.encodeMsg();

	// 将数据写入[写缓冲区]
	_writeBuf->appendPackage(data);
}

// 发送数据
void TcpConnection::addWriteTask(string data) {
	// 将data写入[写缓冲区]
	_writeBuf->appendPackage(data);
#if 0 // 通过[检测写事件]发送数据 -> 适用于数据量不大的情况
	// [1] 让channel只检测写事件
	_channel->setCurrentEvent(FdEvent::_WriteEvent);

	// [2] 修改dispatcher检测的fd的事件 -> 添加修改任务
	_eventLoop->addTask(_channel, ElemType::MODIFY);
#else // 直接发送 -> 适用于数据量大的情况
	_writeBuf->sendData(_channel->getFd());
#endif
}

// 断开连接
void TcpConnection::addDeleteTask() {
	LogDebug("断开了和客户端的连接! connName = %s", _name.c_str());
	_eventLoop->addTask(_channel, ElemType::DELETE);
}

// 通信套接字fd的读回调
int TcpConnection::processRead(void* arg) {
	// 1. 接收数据
	TcpConnection* conn = static_cast<TcpConnection*>(arg);
	int sockfd = conn->_channel->getFd();
	int count = conn->_readBuf->readSockfd(conn->_channel->getFd());
	LogDebug("recved HttpRequest: %s", conn->_readBuf->data());

	// 2. 解析http请求
	if (count > 0) {
		// 解析斗地主数据
		conn->_reply->parseRequest(conn->_readBuf);
	}
	else if (count <= 0) {
		// 和客户端断开连接
		conn->addDeleteTask();
		LogDebug("和客户端的连接断开了...");
	}
	return 0;
}

/*  ∵若监视fd的写事件，则当写缓冲区中有空间时，fd的写事件就会就绪。默认情况下，写缓冲区都是有空间的，
*	∴只要一检测，就会通知fd的写事件就绪了，∴在初始化时不监视fd的写事件，而是在要发送数据时才监视fd的写事件，
*	即在要发送数据时，给反应堆的任务队列添加修改任务，以监视fd的读写事件
*	1. fd的监视事件修改为读写
*	2. 添加fd写事件的回调函数，在回调函数中将数据发送出去
*/

// 通信套接字fd的写回调
int TcpConnection::processWrite(void* arg) {
	TcpConnection* conn = static_cast<TcpConnection*>(arg);
	// 1. 发送数据
	int count = conn->_writeBuf->sendData(conn->_channel->getFd());
	if (count > 0) {
		// 2. 判断数据是否被全部发送出去了
		if (conn->_writeBuf->readableSize() == 0) { // 全部发送出去了 -> 不再检测写事件
			// [1] 修改channel中设置的监视事件: 不再检测写事件, 改为检测读事件
			conn->_channel->setCurrentEvent(FdEvent::_ReadEvent);

			// [2] 修改dispatcher检测的fd集合 -> 添加修改任务
			conn->_eventLoop->addTask(conn->_channel, ElemType::MODIFY);
			LogDebug("数据发送完毕...");
		}
	}
	return 0;
}

// 销毁Channel及其对应的TcpConnection
int TcpConnection::destroy(void* arg) {
	TcpConnection* conn = static_cast<TcpConnection*>(arg);
	if (conn != nullptr) { delete conn; }
	LogDebug("close connection，free resource, connName = %s", conn->_name.data());
	return 0;
}